package rollup

import (
	"bytes"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/consensys/gnark-crypto/ecc"
	"github.com/consensys/gnark-crypto/kzg"
	"github.com/consensys/gnark/backend/groth16"
	"github.com/consensys/gnark/backend/plonk"
	"github.com/consensys/gnark/frontend"
	"github.com/consensys/gnark/frontend/cs/r1cs"
	"github.com/consensys/gnark/frontend/cs/scs"
	"github.com/consensys/gnark/test"
	"github.com/gorilla/websocket"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)

var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{}
var adminKey = "/mnt/e/fabricdev/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/tls/client.key"
var adminCert = "/mnt/e/fabricdev/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/tls/client.crt"
var op = NewOperator()
var contract = GetContract()

/*
Build websocket server for deep learning model request.
*/
func serve(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()

	for {
		mt, message, _ := c.ReadMessage()
		if err != nil {
			continue
		}

		// put requests into channel buffer.
		req, err := DeserializeRequest(message)
		if err == nil {
			op.q.listRequests <- *req
		}

		err = c.WriteMessage(mt, []byte("Request Success"))
		if err != nil {
			log.Println("write:", err)
			break
		}
	}
}

func BiometricsServe() {
	flag.Parse()
	log.SetFlags(0)
	http.HandleFunc("/echo", serve)
	err := http.ListenAndServeTLS(*addr, adminCert, adminKey, nil)
	fmt.Println(err)
}

func Groth16Setup() (cs frontend.CompiledConstraintSystem, pk groth16.ProvingKey, vk groth16.VerifyingKey, err error) {
	var circuit Circuit
	cs, err = frontend.Compile(ecc.BN254, r1cs.NewBuilder, &circuit)
	if err != nil {
		return
	}
	pk, vk, err = groth16.Setup(cs)
	if err != nil {
		fmt.Println(err)
		return
	}

	return cs, pk, vk, err
}

func PlonkSetup() (ccs frontend.CompiledConstraintSystem, srs kzg.SRS, pk plonk.ProvingKey, vk plonk.VerifyingKey, err error) {
	var circuit Circuit
	ccs, err = frontend.Compile(ecc.BN254, scs.NewBuilder, &circuit)
	if err != nil {
		return
	}
	srs, err = test.NewKZGSRS(ccs)
	pk, vk, err = plonk.Setup(ccs, srs)
	if err != nil {
		fmt.Println(err)
		return
	}
	return ccs, srs, pk, vk, err
}

func ZeroKnowlegerProve() {

	cs, pk, vk, err := Groth16Setup()
	// cs, srs, pk, vk, err := PlonkSetup()
	if err != nil {
		fmt.Println(err)
		return
	}

	var vkBuf bytes.Buffer
	vk.WriteTo(&vkBuf)

	// var srsBuf bytes.Buffer
	// srs.WriteTo(&srsBuf)

	count := 0
	for {
		req := <-op.q.listRequests
		op.updateState(req, count)
		count++
		if count == batchSize {

			op.AttackDetect()

			witnessFull, err := frontend.NewWitness(&op.witnesses, ecc.BN254)
			if err != nil {
				fmt.Println("witness", err)
			}

			proof, err := groth16.Prove(cs, pk, witnessFull)
			// proof, err := plonk.Prove(cs, pk, witnessFull)
			if err != nil {
				fmt.Println("proof ", err)
			}

			var proofBuf bytes.Buffer
			proof.WriteTo(&proofBuf)
			// Groth16 public witness
			witnessPublic, err := witnessFull.Public()

			// Plonk public witness
			// witnessPublic, err := frontend.NewWitness(&op.witnesses, ecc.BN254, frontend.PublicOnly())

			if err != nil {
				fmt.Println("witnessPublic", err)
			}
			var witnessPublicBuf bytes.Buffer
			witnessPublicBin, err := witnessPublic.MarshalBinary()
			if err != nil {
				fmt.Println("witnessPublicBin ", err)
			}
			witnessPublicBuf.Write(witnessPublicBin)

			// vk.InitKZG(srs)
			// plonk.Verify(proof, vk, witnessPublic)
			// onchain zkp
			log.Println("--> Submit Transaction: Verify")
			result, err := contract.EvaluateTransaction("VerifyGroth16", vkBuf.String(), proofBuf.String(), witnessPublicBuf.String())
			// result, err := contract.EvaluateTransaction("VerifyPlonk", vkBuf.String(), proofBuf.String(), witnessPublicBuf.String(), srsBuf.String())
			if err != nil {
				log.Fatalf("Failed to Submit transaction: %v", err)
			}
			log.Println(string(result))

			count = 0
		}
	}
}

func GetContract() *gateway.Contract {

	os.RemoveAll("/mnt/e/godev/inftychain/rollup/keystore")
	os.RemoveAll("/mnt/e/godev/inftychain/rollup/wallet")

	err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
	if err != nil {
		log.Fatalf("Error setting DISCOVERY_AS_LOCALHOST environment variable: %v", err)
	}

	wallet, err := gateway.NewFileSystemWallet("wallet")
	if err != nil {
		log.Fatalf("Failed to create wallet: %v", err)
	}

	if !wallet.Exists("appUser") {
		err = populateWallet(wallet)
		if err != nil {
			log.Fatalf("Failed to populate wallet contents: %v", err)
		}
	}

	ccpPath := filepath.Join(
		"/mnt/e/fabricdev/fabric-samples",
		"test-network",
		"organizations",
		"peerOrganizations",
		"org1.example.com",
		"connection-org1.yaml",
	)

	gw, err := gateway.Connect(
		gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
		gateway.WithIdentity(wallet, "appUser"),
	)
	if err != nil {
		log.Fatalf("Failed to connect to gateway: %v", err)
	}
	defer gw.Close()

	channelName := "mychannel"
	if cname := os.Getenv("CHANNEL_NAME"); cname != "" {
		channelName = cname
	}

	log.Println("--> Connecting to channel", channelName)
	network, err := gw.GetNetwork(channelName)
	if err != nil {
		log.Fatalf("Failed to get network: %v", err)
	}

	chaincodeName := "basic"
	if ccname := os.Getenv("CHAINCODE_NAME"); ccname != "" {
		chaincodeName = ccname
	}

	log.Println("--> Using chaincode", chaincodeName)
	contract := network.GetContract(chaincodeName)

	return contract

}

func populateWallet(wallet *gateway.Wallet) error {
	log.Println("============ Populating wallet ============")
	credPath := filepath.Join(
		"/mnt/e/fabricdev/fabric-samples",
		"test-network",
		"organizations",
		"peerOrganizations",
		"org1.example.com",
		"users",
		"User1@org1.example.com",
		"msp",
	)

	certPath := filepath.Join(credPath, "signcerts", "User1@org1.example.com-cert.pem")
	// read the certificate pem
	cert, err := os.ReadFile(filepath.Clean(certPath))
	if err != nil {
		return err
	}

	keyDir := filepath.Join(credPath, "keystore")
	// there's a single file in this dir containing the private key
	files, err := os.ReadDir(keyDir)
	if err != nil {
		return err
	}
	if len(files) != 1 {
		return fmt.Errorf("keystore folder should have contain one file")
	}
	keyPath := filepath.Join(keyDir, files[0].Name())
	key, err := os.ReadFile(filepath.Clean(keyPath))
	if err != nil {
		return err
	}

	identity := gateway.NewX509Identity("Org1MSP", string(cert), string(key))

	return wallet.Put("appUser", identity)
}
